/* 02-prod-cons.c

   All 'W' objects wait for a single 'N' object to complete.  If the user
   specifies that exactly one of the 'W' objects wakes up, one does and 
   finishes leaving the others in limbo.  Otherwise, all 'W' objects wake up
   and finish.

   Usage: 02-prod-cons [ syield | pyield | no_yield | sleep ]

   Note: the 'Monitors' in this case consists of procedures a and b.
   A user may define any number of mutex and cond variables and pass
   references to these variables via objects of type Data to the
   functions that are tied to threads.  In this way any number of
   monitors can be defined.  In this example two monitors are defined,
   W(1) and W(3) wait in one, W(2) waits in another, the N object
   wakes up threads only in the first monitor hence will never wake up
   W(2).

   Differs from 01-prod-cons in that each thread does some work and 
   a yield is invoked by the main thread.
*/
#include <pthread.h>
#include <stdlib.h>
#include <stdio.h>
#include <stdbool.h>

typedef struct {
   int id;
   int monitor;
   pthread_mutex_t *lock;
   pthread_cond_t *notifier;
} Data;

pthread_mutex_t monitor_mutex_1;
pthread_cond_t monitor_wait_1;
pthread_mutex_t monitor_mutex_2;
pthread_cond_t monitor_wait_2;

/* 
   Exactly one active thread is allowed to run in this function at a time.
   When a thread is put into a wait state with pthread_cond_wait it gives
   up its monitor_mutex lock so that another thread may enter the function.
   When a waiting thread is signaled with pthread_cond_signal or
   pthread_cond_broadcast it becomes active and no other thread will be 
   active in the function until it leaves.

   The data argument contains a reference to a mutex and to a cond variable
   so that the same code can apply to any number of monitors.
*/
void saveit (void *data) {
   pthread_mutex_t *lock = ((Data*)data)->lock;
   pthread_cond_t *notifier = ((Data*)data)->notifier;
   int id = ((Data*)data)->id;
   int m = ((Data*)data)->monitor;

   pthread_mutex_lock(lock);
   printf("\tsaveit: wait W(%d) in monitor %d\n", id, m);
   pthread_cond_wait(notifier, lock); 
   printf("\tsaveit: release W(%d) in monitor %d\n", id, m);
   pthread_mutex_unlock(lock);
}

void doit (void *data) {
   pthread_mutex_t *lock = ((Data*)data)->lock;
   pthread_cond_t *notifier = ((Data*)data)->notifier;
   int id = ((Data*)data)->id;
   int m = ((Data*)data)->monitor;
   
   pthread_mutex_lock(lock);
   printf("\tdoit: notifier N(%d) in monitor %d\n", id, m);
   pthread_cond_broadcast(notifier);
   printf("\tdoit: done N(%d) - processes notified\n", id);
   pthread_mutex_unlock(lock);
}

/*
  This is the function that is tied to the 'N' objects
  It chooses a mutex and cond variables depending on the monitor selected
  by the main program.
*/
void *nrun (void *data) {
   int id = ((Data*)data)->id;

   printf("Starting  N(%d)\n", id);

   if (((Data*)data)->monitor == 1) {
      ((Data*)data)->lock = &monitor_mutex_1;
      ((Data*)data)->notifier = &monitor_wait_1;
   } else {
      ((Data*)data)->lock = &monitor_mutex_2;
      ((Data*)data)->notifier = &monitor_wait_2;
   }

   doit(data);
   printf("Finishing N(%d)\n", id);
   pthread_exit(NULL);
}

/*
  This is the function that is tied to the 'W' objects
  It chooses a mutex and cond variables depending on the monitor selected
  by the main program.
*/
void *wrun (void *data) {
   int id = ((Data*)data)->id;
   long i;

   printf("Starting  W(%d)\n", id);

   if (((Data*)data)->monitor == 1) {
      ((Data*)data)->lock = &monitor_mutex_1;
      ((Data*)data)->notifier = &monitor_wait_1;
   } else {
      ((Data*)data)->lock = &monitor_mutex_2;
      ((Data*)data)->notifier = &monitor_wait_2;
   }

   saveit(data);
   for (i=0 ; i < 10000000 ; i++) {
      if ((i%1000000) == 0) printf("%d:\t%d\n",id,i);
      fflush(stdout);
   }
   printf("Finishing W(%d)\n", id);
   pthread_exit(NULL);
}

int main (int argc, char **argv) {
   pthread_t w1,w2,w3,w4;
   pthread_attr_t attr;
   Data t1,t2,t3,t4,t5;
   bool sflag, yflag, pflag;
   int i;

   t1.id = 1;
   t1.monitor = 1;
   t2.id = 2;
   t2.monitor = 1;
   t3.id = 3;
   t3.monitor = 1;
   t4.id = 4;
   t4.monitor = 1;
   t5.id = 101;
   t5.monitor = 1;
   t5.lock = &monitor_mutex_1;
   t5.notifier = &monitor_wait_1;

   if (argc != 2) {
      printf("Usage: 01-prod-cons [ syield | pyield | no_yield | sleep ]\n");
      exit(0);
   }
   
   pthread_mutex_init(&monitor_mutex_1, NULL);
   pthread_mutex_init(&monitor_mutex_2, NULL);
   pthread_cond_init (&monitor_wait_1, NULL);
   pthread_cond_init (&monitor_wait_2, NULL);

   yflag = (!strncmp(argv[1],"syield",6)) ? true : false;
   sflag = (!strncmp(argv[1],"sleep",5)) ? true : false;
   pflag = (!strncmp(argv[1],"pyield",6)) ? true : false;
   
   pthread_attr_init(&attr);
   pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE);
   pthread_create(&w1, &attr, wrun, (void*)&t1);
   pthread_create(&w2, &attr, wrun, (void*)&t2);
   pthread_create(&w3, &attr, wrun, (void*)&t3);
   pthread_create(&w4, &attr, wrun, (void*)&t4);

   if (sflag) sleep(5); 
   else if (yflag) for (i=0 ; i < 200 ; i++) sched_yield();
   else if (pflag) for (i=0 ; i < 200 ; i++) pthread_yield();
   doit((void*)&t5);

   pthread_exit(NULL);
}
